package cn.jpanda.elastic.job.autoconfigure.strategy;

import cn.jpanda.elastic.job.autoconfigure.annotations.*;
import cn.jpanda.elastic.job.autoconfigure.constants.ElasticJobAnnotationConstants;
import cn.jpanda.elastic.job.autoconfigure.exception.ElasticJobConfigurationRuntimeException;
import com.dangdang.ddframe.job.config.JobCoreConfiguration;
import com.dangdang.ddframe.job.executor.handler.JobProperties;
import com.dangdang.ddframe.job.executor.handler.impl.DefaultExecutorServiceHandler;
import com.dangdang.ddframe.job.executor.handler.impl.DefaultJobExceptionHandler;
import com.dangdang.ddframe.job.lite.api.listener.AbstractDistributeOnceElasticJobListener;
import com.dangdang.ddframe.job.lite.api.listener.ElasticJobListener;
import com.dangdang.ddframe.job.lite.config.LiteJobConfiguration;
import com.dangdang.ddframe.job.lite.spring.api.SpringJobScheduler;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;

/**
 * 抽象作业注解解析器
 *
 * @author Hanqi <jpanda@aliyun.com>
 * @since 2018/9/25 17:39
 */
public abstract class AbstractJobParseAnnotationHandlerStrategy implements JobParseAnnotationHandlerStrategy, ApplicationContextAware {

    private ApplicationContext applicationContext;

    /**
     * 获取核心配置定义
     *
     * @param jobBeanDefinition 作业实现类bean定义
     * @param metadata          作业实现类bean定义的元数据
     * @return 作业的核心配置
     */
    public BeanDefinition getJobCoreConfiguration(ScannedGenericBeanDefinition jobBeanDefinition, AnnotationMetadata metadata) {

        BeanDefinitionBuilder jobCoreBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(JobCoreConfiguration.class);
        jobCoreBeanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
        jobCoreBeanDefinition.setLazyInit(true);

        // 配置核心配置
        AnnotationAttributes monitorAttributes = getJobCoreAnnotationAttrs(jobBeanDefinition, metadata);

        // 任务名称
        String jobName = monitorAttributes.getString(ElasticJobAnnotationConstants.ELASTIC_JOB_COMMON_JOB_NAME);
        if (StringUtils.isEmpty(jobName)) {
            // 没有设置作业名称，默认使用作业实现类的全限定名称
            jobName = jobBeanDefinition.getBeanClassName();
        }
        String cron = monitorAttributes.getString(ElasticJobAnnotationConstants.ELASTIC_JOB_COMMON_CRON);
        int shardingTotalCount = monitorAttributes.getNumber(ElasticJobAnnotationConstants.ELASTIC_JOB_COMMON_SHARDING_TOTAL_COUNT);
        String shardingItemParameters = monitorAttributes.getString(ElasticJobAnnotationConstants.ELASTIC_JOB_COMMON_SHARDING_ITEM_PARAMETERS);
        String jobParameter = monitorAttributes.getString(ElasticJobAnnotationConstants.ELASTIC_JOB_COMMON_JOB_PARAMETER);
        boolean failover = monitorAttributes.getBoolean(ElasticJobAnnotationConstants.ELASTIC_JOB_COMMON_FAILOVER);
        boolean misfire = monitorAttributes.getBoolean(ElasticJobAnnotationConstants.ELASTIC_JOB_COMMON_MISFIRE);
        String description = monitorAttributes.getString(ElasticJobAnnotationConstants.ELASTIC_JOB_COMMON_DESCRIPTION);
        jobCoreBeanDefinition.addConstructorArgValue(jobName);
        jobCoreBeanDefinition.addConstructorArgValue(cron);
        jobCoreBeanDefinition.addConstructorArgValue(shardingTotalCount);
        jobCoreBeanDefinition.addConstructorArgValue(shardingItemParameters);
        jobCoreBeanDefinition.addConstructorArgValue(jobParameter);
        jobCoreBeanDefinition.addConstructorArgValue(failover);
        jobCoreBeanDefinition.addConstructorArgValue(misfire);
        jobCoreBeanDefinition.addConstructorArgValue(description);
        jobCoreBeanDefinition.addConstructorArgValue(createJobPropertiesBeanDefinition(metadata));
        return jobCoreBeanDefinition.getBeanDefinition();
    }

    /**
     * 创建作业定义的扩展配置，主要是异常处理和线程池
     *
     * @param metadata 作业配置元数据
     * @return 作业定义的扩展配置bean定义
     */
    private BeanDefinition createJobPropertiesBeanDefinition(AnnotationMetadata metadata) {
        AnnotationAttributes
                JobHandlerAttributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(JobHandler.class.getCanonicalName()));
        // 线程处理器
        String executorServiceHandlers = DefaultExecutorServiceHandler.class.getCanonicalName();
        String exceptionHandlers = DefaultJobExceptionHandler.class.getCanonicalName();
        if (!ObjectUtils.isEmpty(JobHandlerAttributes)) {
            executorServiceHandlers = JobHandlerAttributes.getClass("executorServiceHandlers").getCanonicalName();
            exceptionHandlers = JobHandlerAttributes.getClass("exceptionHandlers").getCanonicalName();
        }

        BeanDefinitionBuilder result = BeanDefinitionBuilder.rootBeanDefinition(JobProperties.class);
        EnumMap<JobProperties.JobPropertiesEnum, String> map = new EnumMap<>(JobProperties.JobPropertiesEnum.class);
        map.put(JobProperties.JobPropertiesEnum.EXECUTOR_SERVICE_HANDLER, executorServiceHandlers);
        map.put(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER, exceptionHandlers);
        result.addConstructorArgValue(map);
        return result.getBeanDefinition();
    }

    /**
     * 获取作业核心注解配置的属性集合
     *
     * @param jobBeanDefinition 作业配置
     * @param metadata          元数据
     * @return 作业核心注解配置的属性集合
     */
    protected abstract AnnotationAttributes getJobCoreAnnotationAttrs(ScannedGenericBeanDefinition jobBeanDefinition, AnnotationMetadata metadata);

    protected abstract BeanDefinition getJobTypeConfigurationBeanDefinition(ScannedGenericBeanDefinition jobBeanDefinition
            , AnnotationMetadata metadata);

    /**
     * 创建lite作业配置
     *
     * @param jobBeanDefinition 作业配置定义
     * @param metadata          元数据
     * @return lite作业配置
     */
    private BeanDefinition createLiteJobConfigurationBeanDefinition(ScannedGenericBeanDefinition jobBeanDefinition, AnnotationMetadata metadata) {
        //获取监控配置
        AnnotationAttributes
                monitorAttributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(JobMonitor.class.getCanonicalName()));
        boolean monitorExecution = false;
        int monitorPort = -1;
        if (!ObjectUtils.isEmpty(monitorAttributes)) {
            //配置监控
            monitorExecution = monitorAttributes.getBoolean(ElasticJobAnnotationConstants.MONITOR_ATTR_MONITOR_EXECUTION);
            monitorPort = monitorAttributes.getNumber(ElasticJobAnnotationConstants.MONITOR_ATTR_MONITOR_PORT);
        }

        // 配置核心配置
        AnnotationAttributes jobCoreAttributes = getJobCoreAnnotationAttrs(jobBeanDefinition, metadata);

        int maxTimeDiffSeconds = -1;
        maxTimeDiffSeconds = jobCoreAttributes.getNumber("maxTimeDiffSeconds");
        String jobShardingStrategyClass = jobCoreAttributes.getString("jobShardingStrategyClass");
        int reconcileIntervalMinutes = 10;
        reconcileIntervalMinutes = jobCoreAttributes.getNumber("reconcileIntervalMinutes");

        boolean disabled = jobCoreAttributes.getBoolean("disabled");
        boolean overwrite = jobCoreAttributes.getBoolean("overwrite");

        BeanDefinitionBuilder result = BeanDefinitionBuilder.rootBeanDefinition(LiteJobConfiguration.class);
        result.addConstructorArgValue(this.getJobTypeConfigurationBeanDefinition(jobBeanDefinition, metadata));
        result.addConstructorArgValue(monitorExecution);
        result.addConstructorArgValue(maxTimeDiffSeconds);
        result.addConstructorArgValue(monitorPort);

        result.addConstructorArgValue(jobShardingStrategyClass);
        result.addConstructorArgValue(reconcileIntervalMinutes);
        result.addConstructorArgValue(disabled);
        result.addConstructorArgValue(overwrite);
        return result.getBeanDefinition();
    }

    /**
     * 获取作业
     *
     * @param beanDefinition 作业配置定义
     * @param metadata       元数据
     * @return 作业
     */
    @Override
    public BeanDefinition getJobScheduler(ScannedGenericBeanDefinition beanDefinition, AnnotationMetadata metadata) {
        BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(SpringJobScheduler.class);
        factory.setLazyInit(true);
        factory.setInitMethodName("init");
        factory.addConstructorArgValue(beanDefinition);

        // 获取配置中心
        AnnotationAttributes
                registerCenterAttributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(RegistryCenter.class.getCanonicalName()));
        Class registerCenter = ElasticJobAnnotationConstants.DEFAULT_REGISTER_CENTER;
        if (!ObjectUtils.isEmpty(registerCenterAttributes)) {
            registerCenter = registerCenterAttributes.getClass("registerCenter");
        }
        factory.addConstructorArgReference(getBeanNameByType(registerCenter));

        // 配置Lite作业配置
        factory.addConstructorArgValue(createLiteJobConfigurationBeanDefinition(beanDefinition, metadata));
        AnnotationAttributes
                jobEventAttributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(JobEvent.class.getCanonicalName()));

        // 配置作业事件
        if (!ObjectUtils.isEmpty(jobEventAttributes)) {
            boolean enableJobEvent = jobEventAttributes.getBoolean("enabled");
            if (enableJobEvent) {
                factory.addConstructorArgReference(getBeanNameByType(jobEventAttributes.getClass("eventHandlerClass")));
            }
        }
        // 配置作业监听器
        factory.addConstructorArgValue(createListeners(metadata));
        return factory.getBeanDefinition();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 创建作业监听器集合
     *
     * @param metadata 元数据
     * @return 作业监听器集合
     */
    public List<BeanDefinition> createListeners(AnnotationMetadata metadata) {

        // ManagedList
        List<BeanDefinition> beanDefinitions = new ManagedList<>();
        AnnotationAttributes
                jobListenerAttributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(JobListener.class.getCanonicalName()));

        // 配置作业监听器
        if (!ObjectUtils.isEmpty(jobListenerAttributes)) {
            Class[] listeners = jobListenerAttributes.getClassArray("listenClass");
            for (Class clazz : listeners) {
                if (ClassUtils.isAssignable(ElasticJobListener.class, clazz)) {
                    BeanDefinitionBuilder listener = BeanDefinitionBuilder.genericBeanDefinition(clazz);
                    if (ClassUtils.isAssignable(AbstractDistributeOnceElasticJobListener.class, clazz)) {
                        //获取注解的时间配置
                        listener.addConstructorArgValue(jobListenerAttributes.getNumber("startedTimeoutMilliseconds"));
                        listener.addConstructorArgValue(jobListenerAttributes.getNumber("completedTimeoutMilliseconds"));
                    }
                    listener.setScope(BeanDefinition.SCOPE_PROTOTYPE);
                    beanDefinitions.add(listener.getBeanDefinition());
                }

            }
        }

        return beanDefinitions;
    }

    /**
     * 根据指定的Class类型,获取bean名称
     *
     * @param beanType 指定的Class类型
     * @return bean名称
     */
    private String getBeanNameByType(Class beanType) {
        String[] beanNames = applicationContext.getBeanNamesForType(beanType);
        if (beanNames.length == 0) {
            throw new ElasticJobConfigurationRuntimeException("There must be an instance of " + beanType.getCanonicalName() + " in spring container.");
        }
        if (beanNames.length > 1) {
            throw new ElasticJobConfigurationRuntimeException("Only one instance of "+beanType.getCanonicalName()+" is needed,but two are find:" + Arrays.toString(beanNames) + ".");
        }
        return beanNames[0];
    }
}
